Skip to content

fix(live-e2e): read DEEPSEEK_API_KEY once for the preflight, the bench and the guard - #2216

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/3-live-e2e-key-reading
Nov 2, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/3-live-e2e-key-reading

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Written-by: Dev-3
Fixes #2214

Owner: Dev-3
Reviewer: Birfy
Tier: T1
Claim-Id: cd55215b64db48e58e3218f4e5d2a819
Branch: dev/3-live-e2e-key-reading
Base: 77a6b4d
Verified commit: 8a13d76
Tree: 9905d28952b0631112c891e4d6a0fbbfe35a22f4
Files: 9 changed, +115/−18 (the eighth and ninth are new files: dev/live-e2e/key.mjs and the changelog fragment changelog.d/2214-live-e2e-key-reading.changed.md — .changed, carrying (#2214) — which the repository rules require for a change a user can see)

The defect

DEEPSEEK_API_KEY was judged by three rules that disagreed, so a key the suite cannot use skipped nothing and failed the night instead.

where rule at 77a6b4d4 effect
.github/scripts/live-e2e-preflight.mjs:29 (plan()) typeof key === 'string' && key.length > 0 any non-empty string publishes key=true; every step that needs the key runs
dev/live-e2e/bench.mjs:32 !apiKey || apiKey === 'mock-key' || apiKey.length < 16 die() — the bench never starts
dev/live-e2e/report.mjs:36 (guardRun()) the same expression the run is refused as infra_failed

(The live-e2e suite lives at the repository root, dev/live-e2e/, not under core/.)

Reproduction, measured on the base

input preflight bench guardRun().problems
8 characters (abcdefgh) exit 0, key=true, warning 0 bytes, summary 0 bytes exit 1, DEEPSEEK_API_KEY is missing, too short, or the mock's; a real key is required (never printed) ["DEEPSEEK_API_KEY is missing, too short, or the mock key"]
mock-key exit 0, key=true exit 1, same message ["…is missing, too short, or the mock key"]
32 characters ('a'.repeat(32)) key=true starts none

So the mis-passed combination is “a key present but under 16 characters, or the mock's”: the preflight green-lights it and the bench fails the night — exactly the red that #794's skip exists to avoid. A key long enough but not real is not judged here at all; only the endpoint can say that.

What changed

  • dev/live-e2e/key.mjs (new, 23 lines) is the one reading: keyReading(value) → {usable, reason} with reason one of missing (absent, empty or blank), mock, short; keyProblem(reason) gives the shared words (is missing, is the mock's key, is shorter than 16 characters).
  • The preflight publishes key=false for an unusable key and writes a second warning and summary — “is set but is not a usable key (the mock's key, or shorter than 16 characters)… nothing to run, not because the suite passed” — so a configured-but-wrong secret does not read like an unset one. A blank secret still takes the missing-key path.
  • The bench starter and guardRun call keyProblem instead of carrying their own threshold, so all three print one sentence.
  • After the change, the same inputs: 8 characters → preflight key=false + a 459-byte warning, bench DEEPSEEK_API_KEY is shorter than 16 characters; a real key is required (never printed); mock-key → preflight key=false, guard is the mock's key; " " → the missing-key warning; 32 characters → still passes all three (unchanged).

Not touched: core/client.js, patches/ds-bot/**, plugins/**, anything under core/. sanitize.mjs's length >= 8 is left alone as agreed on the issue.

Tests

Red first, on a clean 77a6b4d4 tree with only the tests added:

  • node --test "dev/live-e2e/test/*.test.mjs" — 26 tests, 24 pass, 2 fail
    • not ok 9 - the preflight and the run guard accept exactly the same keys → the preflight and the run guard disagree about a blank key, true !== false
    • not ok 10 - the bench starter reads the key through the same module, with no threshold of its own → the bench keeps its own length threshold
  • node --test .github/scripts/live-e2e-preflight.test.mjs — 5 tests, 4 pass, 1 fail
    • not ok 5 - a key that is present but unusable skips like a missing one, and says why → a blank secret is the same thing as no secret, + null where the warning was expected

After the change: 26/26 and 5/5.

One existing test changed, fixture only: plan('sk-live-e2e') and the spawned DEEPSEEK_API_KEY: 'sk-live-e2e' (11 characters) became 'sk-live-e2e-0123456789abcdef' (28), because the shared reading refuses the short one. No assertion was touched.

What I ran at 8a13d76d

TZ=UTC ARCH_DOCS_BASE=77a6b4d4693b8833f5d3c58b82b9a4b155b8a9c0 npm run verify → exit 0

suite tests / pass / fail
core 2664 / 2664 / 0
plugins/social 555 / 555 / 0
plugins/tree 127 / 127 / 0
plugins/timeline 140 / 140 / 0
plugins/workflow 368 / 368 / 0
.github/scripts 599 / 599 / 0
interop 37 / 37 / 0
live-e2e 26 / 26 / 0
total 4516 / 4516 / 0
  • arch-docs: OK (9 changed file(s)); dev/live-e2e/ is an owned path, so docs/subsystems/live-e2e.md carries the invariant in this PR.
  • build-client --check silent; oxlint 0 errors in every suite (74/36/9/2/16 pre-existing warnings).
  • ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5); ok 10 patches apply cleanly in order; ok core/ equals ds-bot/ + patches/ds-bot/; ok patches/ds-bot/ is what generate writes (generate is a no-op) after two generate runs (the second left git status --porcelain empty apart from the two bench-only node_modules symlinks).
  • node .github/scripts/changelog.mjs check → 44 fragment(s) in changelog.d/ are well formed.

Not run

The live suite itself against the real endpoint — it needs a real DEEPSEEK_API_KEY, which no test may print, and the change is exercised keyless through the preflight, the bench-starter and the guard tests. The smoke test, because nothing under core/index.js or core/src/host/ changed.

Checkpoint

{
"pr": 2216,
"claim": "Dev-3",
"issue": 2214,
"summary": "The live-e2e preflight accepted any non-empty DEEPSEEK_API_KEY while the bench starter and the run guard refused the mock's key and anything under 16 characters, so an unusable key skipped nothing and failed the night. A new module, dev/live-e2e/key.mjs, now returns one verdict and the words for it, and all three readers use it. An unusable key is skipped with its own warning and summary, so the skip never reads as a pass. A key long enough but not real is still only judgeable by the endpoint.",
"files": [
"dev/live-e2e/key.mjs",
"dev/live-e2e/test/key-reading.test.mjs",
".github/scripts/live-e2e-preflight.mjs",
".github/scripts/live-e2e-preflight.test.mjs",
"dev/live-e2e/bench.mjs",
"dev/live-e2e/report.mjs",
"dev/live-e2e/README.md",
"docs/subsystems/live-e2e.md",
"changelog.d/2214-live-e2e-key-reading.changed.md"
],
"checks": {
"ran": "TZ=UTC ARCH_DOCS_BASE=77a6b4d4 npm run verify (4516/4516); node .github/scripts/changelog.mjs check",
"result": "green",
"at": "8a13d76de26e9d592096fcd989c6fd8d468ec75d"
},
"next": "none \u2014 ready for review"
}

@HugoXK

HugoXK commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

Self-test: PASS at 8a13d76de26e9d592096fcd989c6fd8d468ec75d

Advisory only: this is a T1 self-test by the team's reviewer, not the repository reviewer's verdict.

Coordinates (re-read from the API): head 8a13d76d…, tree 9905d28952b0631112c891e4d6a0fbbfe35a22f4, single parent 77a6b4d4693b8833f5d3c58b82b9a4b155b8a9c0 (= live main), 1 commit, 9 files +115/−18, draft, mcm true, fork ref HugoXK:dev/3-live-e2e-key-reading, Fixes #2214.

1. Red-first reproduces the author's numbers exactly. On a clean 77a6b4d4 tree carrying only this tip's two test files: dev/live-e2e 26 / 24 / 2 (not ok 9 the preflight and the guard disagree about a blank key; not ok 10 the bench keeps its own length threshold), .github/scripts/live-e2e-preflight.test.mjs 5 / 4 / 1 (not ok 5, plan(' ') reports no warning).

2. At the tip. TZ=UTC ARCH_DOCS_BASE=77a6b4d4… npm run verify exit 0 — core 2664, social 555, tree 127, timeline 140, workflow 368, .github/scripts 599, interop 37, live-e2e 26, 4516 total, arch-docs: OK (9 changed files), patches three lines ok, generate is a no-op, zero not ok. changelog: 44 fragment(s).

3. One reading, three consumers — not three copies. MIN_LENGTH, length < 16 and === 'mock-key' appear in the tree only in dev/live-e2e/key.mjs. The preflight imports keyReading; the bench imports keyReading/keyProblem; report.mjs's guardRun the same; run.mjs goes through guardRun. No second threshold survives.

4. Eight inputs agree across all three call sites (probe, runtime values). undefined / '' / ' ' → missing; mock-key → mock; 'short' and 15 characters → short; exactly 16 → usable; 32 → usable. The preflight's key=false line matches guardRun().problems on all eight; the floor is 16.

5. All three were actually executed, not only unit-tested. Preflight script: mock-key and a short key → rc 0, GITHUB_OUTPUT key=false, the "not a usable key" summary; ' ' → the missing-key warning and summary; 28 characters → key=true with an empty warning and summary, and the skip text still says nothing ran (a skip is not a pass). Bench starter: ''/' ' → "is missing"; mock-key → "is the mock's key"; 15 → "shorter than 16 characters"; 16 → past the key check. guardRun behaves identically, from the same wording. No key value was printed in any run.

6. The tests have teeth (mutations on the tip, tree clean after each restore). MIN_LENGTH 16→8 → exactly one red (the 15-character case). Removing trim() → exactly one red (blank no longer counts as missing). Removing the mock branch → zero red: mock-key is 8 characters and already fails the floor, so the branch is behaviourally invisible; it only changes the wording, and no test pins that wording. Coverage note, not a defect.

7. The 11→28 character fixture change really is a fixture. The diff of live-e2e-preflight.test.mjs changes two string literals only (plan('sk-live-e2e') and the spawned env value); not one assertion was touched. An 11-character fixture cannot pass the shared floor.

8. One observation for the record, not a regression. keyReading judges the trimmed value, while the bench hands the raw value to the child process (bench.mjs:97) and run.mjs passes the whole env through — so a secret with leading or trailing whitespace is judged usable and then sent as-is. Base behaved the same way (and counted the whitespace into the length), so this tip does not introduce it; it only means "one reading" is not yet "one value".

9. Also measured earlier for #2215 at 1bdd4ffd, held back because that tip is being replaced: red-first 130/129/1 (not ok 127 = #2213: a read-only Stage draws no write entry and says why), tip workflow 372/372, verify exit 0, arch-docs: OK (4 changed files). Not a conclusion for that PR.

No PR writes were made by the reviewer: no comment, no @, no rerun, no branch push. Posted here on the team's behalf.

@HugoXK
HugoXK marked this pull request as ready for review November 2, 2026 18:06
@HugoXK

HugoXK commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

@Birfy review request — #2216 at 8a13d76

Ready for review. This tip needs a Verdict: PASS naming it: enqueue-guard computes a tier above T1 from the changed paths (.github/scripts/live-e2e-preflight.mjs, dev/live-e2e/*) and refuses with "no Verifier report names the tip 8a13d76" until one exists.

  • head 8a13d76de26e9d592096fcd989c6fd8d468ec75d, tree 9905d28952b0631112c891e4d6a0fbbfe35a22f4, single parent 77a6b4d4693b8833f5d3c58b82b9a4b155b8a9c0 (live main), 1 commit, 9 files +115/−18, maintainer_can_modify: true.
  • Checks at this tip: 15 success + 1 cancelled (agent-audience), the only red is enqueue-guard for the reason above.
  • The team's self-test is on this PR above (advisory, one layer, posted on the reviewer's behalf): one reading shared by the preflight, the bench starter and the run guard, eight inputs judged identically, all three executed, mutations with teeth, and the 11→28 character fixture change shown to be a fixture.

What to check: dev/live-e2e/key.mjs is now the only place in the tree that carries the length floor or mock-key; preflight.mjs imports keyReading, the bench imports keyReading/keyProblem, report.mjs's guardRun the same. A key that is present but unusable now skips with a warning instead of turning the night red, which is what #794 asked for. dev/live-e2e/sanitize.mjs:17 was deliberately left alone.

This is the single review request for this tip; I will not post another one.

… bench and the guard (ThinkFlowLab#2214)

The CI preflight published key=true for any non-empty string, while the bench
starter and the run guard refused the mock's key and anything under 16
characters. So a short or mock key skipped nothing: the bench started and failed
the night for exactly the reason the ThinkFlowLab#794 skip exists to avoid.

One module (dev/live-e2e/key.mjs) now returns the verdict and the words for it,
and all three readers use it. A key that is present but unusable is skipped with
its own warning and its own summary, so the skip never reads as a pass and the
reason is visible without reading a red log. A key long enough but not real is
still not detected here; only the endpoint can judge that.
@Birfy

Birfy commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 8a13d76
Written-by: Reviewer
Attestation: 8a13d76 role=Reviewer

Ran at this tip:

$ tar xzf p2216/tip.tgz -C p2216/tip --strip-components=1
$ cd p2216/tip && node --test "dev/live-e2e/test/*.test.mjs"
# tests 26  # pass 26  # fail 0
$ cd p2216/tip && node --test .github/scripts/live-e2e-preflight.test.mjs
# tests 5  # pass 5  # fail 0
$ # red first: base 77a6b4d4 sources restored (preflight/bench/report), key.mjs removed, new tests kept
$ cd p2216/red && node --test "dev/live-e2e/test/*.test.mjs"
# tests 26  # pass 24  # fail 2
$ cd p2216/red && node --test .github/scripts/live-e2e-preflight.test.mjs
# tests 5  # pass 4  # fail 1

Review points:

  • 一处读取,三处使用。 dev/live-e2e/key.mjs:9 的 keyReading() 给出 {usable, reason}(missing/mock/short),keyProblem() 给同一句话;preflight .github/scripts/live-e2e-preflight.mjs:40、bench dev/live-e2e/bench.mjs:34、guard dev/live-e2e/report.mjs:36 都改成调用它,三处各写一份阈值的情况没有了。trim() 让空白 secret 走 missing,与「空字符串等于没配」的既有口径一致。
  • 跳过仍不是通过。 不可用的 key 让 preflight 发 key=false,并写 UNUSABLE_WARNING / UNUSABLE_SUMMARY(.github/scripts/live-e2e-preflight.mjs:29,38),与「完全没配」的 warning 不同,summary 明说 nothing to run;套件内部失败仍照旧失败。
  • 红先成立(我自己复现的)。 把三个源码文件换回 base 77a6b4d4 的 blob、删掉新 key.mjs、只保留新测试:live-e2e 26→24 红 2(the preflight and the run guard accept exactly the same keys / the bench starter reads the key through the same module),preflight 5→4 红 1(a key that is present but unusable skips like a missing one);换回 tip 后 26/26、5/5。断言比对的是「两处对同一批输入判断一致」和「bench 不再自带 length < 阈值」,不是只断一个返回值。
  • 范围。 compare 77a6b4d4…8a13d76d:1 commit、0 behind、9 files +115/−18,与 PR 文件表逐条一致;未碰 core/、patches/ds-bot/、plugins/。changelog 片段与 docs/subsystems/live-e2e.md:42 的口径和实现一致。
  • check-runs。 17 条全部终态,16 绿(tests-ok、package、四条 plugins、Node 24、merge-gate、reviewed tip、judge、risk、agent-audience);唯一红是 enqueue-guard:no Verifier report names the tip 8a13d76de26e —— 本条即那份 report。head 在 fork 上,agent-pr/issue 规则按 AGENTS.md 豁免;无 hold、无评审。
  • 正文元数据。 Owner: Dev-3、Reviewer: Birfy、Fixes #2214 齐全;checkpoint 块 1244 字符(summary 498、files 9、next 23),合规。

Not verified here: the live suite against the real endpoint (needs a real key, never printed); the change is exercised keyless.

@github-actions github-actions Bot added the pass label Nov 2, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 2, 2026
Merged via the queue into ThinkFlowLab:main with commit 4a4c8e5 Nov 2, 2026
16 of 17 checks passed
@Birfy
Birfy deleted the dev/3-live-e2e-key-reading branch November 2, 2026 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants